﻿using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;

using Microsoft.Practices.Composite.Events;

using Matchingo.Common.Constants;
using Matchingo.Common.Types.Enumerations;
using Matchingo.Common.Types.EventArguments;
using Matchingo.Common.Types.Events;
using Matchingo.Common.Types.Interfaces;
using Matchingo.Common.Types.Types;
using Matchingo.Common.UIHelpers;

using System.Windows.Browser;

namespace Matchingo.Controllers.GameExecutionController
{
    public class GameExecutionFacade : IGameExecutionFacade
    {
        #region ========== Private Properties ==========

		private IEventAggregator EventAggregator { get; set; }        
        private IMemoryGameLogic GameLogic { get; set; }
        private IMenuFlowProvider MenuProvider { get; set; }        
        private IGameSettingsProvider SettingsProvider { get; set; }
		private IGameInputFacade GameInputFacade { get; set; }
        private IArtificialPlayer AIPlayer { get; set; }
		private IHighScoreProvider HighScoreProvider { get; set; }

        // all UI Elements hide behind this, we hide it when loading is done
        private UIElement UIMaskingElement { get; set; }
        
        // all interactive elements of the game hide behind this, prevents interction on AI and replay games
        private UIElement AIBlockingElement { get; set; } 

        #endregion

        #region ========== Constructors ==========

        public GameExecutionFacade(IEventAggregator eventAggregator, IMemoryGameLogic gameLogic, 
            IMenuFlowProvider menuProvider, IGameSettingsProvider settingsProvider, 
			IArtificialPlayer aiPlayer, IGameInputFacade inputFacade,
            IGameUIContainer uiContainer, IHighScoreProvider highScoreProvider)
        {            
            SettingsProvider = settingsProvider;
            GameLogic = gameLogic;
            MenuProvider = menuProvider;
            AIPlayer = aiPlayer;
			GameInputFacade = inputFacade;
            EventAggregator = eventAggregator;
            UIMaskingElement = uiContainer.UIMaskingElement;
            AIBlockingElement = uiContainer.InputMaskingElement;
			HighScoreProvider = highScoreProvider;
        }

        #endregion

        #region ========== Public Methods ==========

        public void StartGameEngineExecution()
        {
            // make sure the UI mask isn't hit visible, otherwise when we dim it out the user cant click anythign til after its totally clear
            UIMaskingElement.IsHitTestVisible = false;

            // when the MenuProvider finishes initializing we want to fade out the blocking element, then slite in the title page
            MenuProvider.FinishedInitializing += (s, e) =>
                {
                    Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
                    {
                        ShowMenu(MenuModuleNames.ModuleNames.TitleScreenMenu);

                        // build a storyboard to fade out the masking element
                        // TODO:: this is a dependency, should be taken in via constructor (make sure to remove the project dependency on the silverlight helper
                        Storyboard sb = TransparencyHelper.BuildTransparencyTransition(UIMaskingElement, 0.00D, 
                            new TimeSpan(0, 0, 0, 0, CommonConstants.TIME_BETWEEN_GAME_STARTUP_AND_FIRST_AI_GAME), false);
                        sb.Completed += (s2, e2) => 
                        { 
                            UIMaskingElement.Visibility = Visibility.Collapsed;

                            // its feasible the user already started a new game, so make sure he hasn't before kicking a demo one off
                            if (GameLogic.CurrentGame == null)
                            {
								StartNewGame(true, false); // kick off an AI player to play in the background
                            }
                        };
                        sb.Begin();
                    });
                };
            MenuProvider.Initialize();

            EventAggregator.GetEvent<GameEndedEvent>().Subscribe(GameLogic_GameEnded);
        }

        public void StartNewGame(bool aiPlayer, bool closeMenu)
        {
            StartGame(null, aiPlayer, closeMenu);
        }

        public void StartGame(Game replayGame, bool aiPlayer, bool closeMenu)
        {
            #region Sanity Checks

            if (replayGame != null)
            {
                if (replayGame.CardsSet == null || replayGame.CardsSet.Count < 1)
                    throw new ArgumentException("GameExecutionFacade.StartGame() - CardSet is invalid");
            }

            #endregion

            AIPlayer.StopPlayingGame(); // make sure no AI player is playing
            AbortPlayback(); // make sure no replay is playing

            if (replayGame == null)
            {
                GameLogic.StartNewGame(new MatchGameSettings
                {
                    AIGame = aiPlayer,
                    CardCount = SettingsProvider.GameSettings.CardCount,

                    // change the player name for AI games
                    PlayerName = (aiPlayer ? CommonConstants.GAME_SETTINGS_AI_PLAYERNAME : SettingsProvider.GameSettings.PlayerName) 
                });
            }
            else // this is a replay game
            {
                GameLogic.StartNewGame(new MatchGameSettings
                {
                    AIGame = true,					
                    CardCount = replayGame.CardCount,
                    PlayerName = replayGame.PlayerName
                }, replayGame.CardsSet);
            }

            // if this is an AI game then we want to disable user input
            EnableUserInput(!aiPlayer);

            // if this is an AI game (and not a replay) then we want to setup an AI player
            if (aiPlayer && replayGame == null)
            {
                AIPlayer.PlayGame(CommonConstants.TIME_AIPlayer_DelayBetweenTurns);
            }

            // hide the menu
            if (closeMenu)
            {
                MenuProvider.CloseMenu();
            }
        }

        // TODO:: these are just wrappers to the MenuProvider, can we get all other classes to stop accessing MenuProvider?
        public void ShowMenu(MenuModuleNames.ModuleNames menu)
        {
            MenuProvider.OpenMenu(menu);
        }

        public void CloseMenu()
        {
            MenuProvider.CloseMenu();
        }

        public void PlaybackGame(Game game)
        {
            #region Sanity Checks

            if (game == null || game.GameFlipCommands == null || game.GameFlipCommands.Count < 2)
            {
                throw new ArgumentException("GameExecutionFacade.PlaybackGame() called with invalid game to playback.");
            }

            if (game.CardsSet == null || game.CardsSet.Count < 2)
            {
                throw new ArgumentException("GameExecutionFacade.PlaybackGame() called with invalid game cards to playback.");
            }

            #endregion

            // we need to make all the cards face down (they would be face up from a completed game)
            foreach (Card c in game.CardsSet)
            {
                // TODO:: when we new up a card we should reset these things, or should we have a card.reset or something similar?
                c.IsDealt = false;
                c.IsMatched = false;
                c.VisibleSide = Card.CardSide.Rear;
            }

            // we need to close the menu
            MenuProvider.CloseMenu();

            // we do this now instead of after the following pause so that the currently playing game instantly stops
            StartGame(game, true, false);

            // wait for the new game to get started before starting back the playback
            DispatcherTimer dtPauseForStartup = new DispatcherTimer();
            dtPauseForStartup.Interval = new TimeSpan(0, 0, 0, 0, CommonConstants.TIME_REPLAY_DelayBeforePlaybackToAllowMenusToClose); 
            dtPauseForStartup.Tick += (s2, e2) =>
            {               
                StartPlayBack(game);
                dtPauseForStartup.Stop();
            };
            dtPauseForStartup.Start();
        }

        public void AbortPlayback()
        {
            PlaybackAborted = true;
        }

        #endregion

        #region ========== Private Methods ==========

        private void EnableUserInput(bool enabled)
        {
            AIBlockingElement.Visibility = enabled ? Visibility.Collapsed : Visibility.Visible;
        }

        private bool PlaybackAborted { get; set; }

        private void StartPlayBack(Game game)
        {
            PlaybackAborted = false;

            // the first move starts at Zero, so go ahead and fire off the first command
            int index = 0;
            GameLogic.Flip(game.GameFlipCommands[index].UniqueCardID);
            index++;

            DispatcherTimer dt = new DispatcherTimer();
            dt.Interval = game.GameFlipCommands[index].TimeOffset - game.GameFlipCommands[index - 1].TimeOffset;
            dt.Tick += (s, e) =>
            {
                // if the playback was aborted then stop the timer and break out
                if (PlaybackAborted || index >= game.GameFlipCommands.Count)
                {
                    dt.Stop();

                    // when playback ends pull the highscore menu back up (only if it wasnt aborted via another game starting)
					if (index >= game.GameFlipCommands.Count)
					{
						ShowMenu(MenuModuleNames.ModuleNames.HighScoreMenu);
					}

                    return;
                }

                // fire off the event
				GameInputFacade.FlipCard(game.GameFlipCommands[index].UniqueCardID);
                //GameLogic.Flip(game.GameFlipCommands[index].UniqueCardID);

                // setup the next tick to delay however long the next time offset is
                dt.Interval = game.GameFlipCommands[index].TimeOffset - game.GameFlipCommands[index - 1].TimeOffset;

                index++; // increment our index

				// if this was our last move then set the time elapsed to the final time of the replayed game
				// so that the timer matches how long the game actually took (not our playback's timer)
				if (index == game.GameFlipCommands.Count)
				{
					GameLogic.CurrentGame.EndTime = GameLogic.CurrentGame.StartTime + game.GameTimeElapsed;
				}
            };
            dt.Start();
        }

        #endregion

        #region ========== Event Handling ==========

        // NOTE:: must be public to be used by EventAggregator in Silverlight
        public void GameLogic_GameEnded(GameEndedEventArgs args)
        {
            // we only want to pull up the menu if this was an AI game, other wise we might
            // interupt the user already doing something in the menu
            if (!GameLogic.CurrentGame.ArtificialGame)
            {
                DispatcherTimer dtShowTitlePage = new DispatcherTimer();
                dtShowTitlePage.Interval = new TimeSpan(0, 0, 0, 0, CommonConstants.TIME_GAME_ENDED_DelayBeforeMenu); 
                dtShowTitlePage.Tick += (s, e2) =>
                    {
                        // pull up the menu when the game ends
                        ShowMenu(MenuModuleNames.ModuleNames.HighScoreMenu);
                        dtShowTitlePage.Stop();
                    };
                dtShowTitlePage.Start();
            }

            DispatcherTimer dtStartNewGame = new DispatcherTimer();
            dtStartNewGame.Interval = new TimeSpan(0, 0, 0, 0, CommonConstants.TIME_GAME_ENDED_DelayBeforeNewAIGame); 
            dtStartNewGame.Tick += (s, e2) =>
                {
                    // its possible the user started a new game already, if so dont start another
                    if (GameLogic.CurrentGame != null && GameLogic.CurrentGame.GameState != Game.GameStateEnum.Started)
                    {
                        StartNewGame(true, false); // kick off an AI player to play in the background
                    }

                    dtStartNewGame.Stop();
                };
            dtStartNewGame.Start();
        }

        #endregion
    }
}
